package sim.app.geo.haiti;



import java.util.ArrayList;
import java.util.PriorityQueue;
import sim.field.grid.IntGrid2D;
import sim.field.grid.ObjectGrid2D;
import sim.util.Bag;



public class Utilities
{
    int maxCost = 10000;


    static public void gridReplace(IntGrid2D grid, int from, int to)
    {
        for (int i = 0; i < grid.getWidth(); i++)
        {
            for (int j = 0; j < grid.getHeight(); j++)
            {
                if ( grid.get(i, j) == from )
                {
                    grid.set(i, j, to);
                }
            }
        }
    }

    public IntGrid2D constructGradient(Center c, IntGrid2D roads, ObjectGrid2D locations,
                                       int gridWidth, int gridHeight)
    {

        // set up initially with everything as expensive as possible
        IntGrid2D result = new IntGrid2D(gridWidth, gridHeight, maxCost);

        Location center = c.loc;
        result.set(center.x, center.y, 0); // the origin!

        // initialize the mechanism to search
        PriorityQueue<TileWrapper> queue = new PriorityQueue<TileWrapper>();
        ArrayList<Location> doNotAdd = new ArrayList<Location>();
        Bag neighbors = new Bag();

        // process neighbors of the original center
        locations.getNeighborsHamiltonianDistance(
            center.x, center.y, 1, false, neighbors, null, null);

        // we don't ever need to check the center again: we know its cost
        doNotAdd.add(center);

        // check the neighbors and start building the list of tiles to reset
        for (Object o : neighbors)
        {

            Location l = (Location) o;
            if (l == center)
            {
                continue; // don't recheck the center
            }
            int cost = roadCost(l, roads); // set up its cost
            result.set(l.x, l.y, cost);

            // the immediate neighbors of the center need never be checked
            doNotAdd.add(l);

            // ...but THEIR neighbors should be checked
            Bag neighborsNeighbors = new Bag();
            locations.getNeighborsHamiltonianDistance(
                l.x, l.y, 1, false, neighborsNeighbors, null, null);
            for (Object p : neighborsNeighbors)
            {

                Location n = (Location) p;
                if (n == l)
                {
                    continue; // don't recheck this tile!
                }
                // if the location has already been found at this point, it's
                // either the center or one of its neighbors. Don't consider it.
                if (doNotAdd.contains(n))
                {
                    continue;
                }

                // otherwise establish its base cost and save it
                int nCost = cost + roadCost(n, roads);
                result.set(n.x, n.y, nCost);

                // insert it into the queue so that it gets considered again
                queue.add(new TileWrapper(n, nCost));
                doNotAdd.add(n); // it's in the queue: don't have multiple copies there
            }

        }


        // start at center. Push out, expanding along the "cheapest" route
        while (queue.size() > 0)
        {

            // take cheapest node in the queue and get its info
            TileWrapper t = queue.remove();
            int myCost = result.get(t.l.x, t.l.y);
            int myType = roadCost(t.l, roads);

            // check its neighbors: is there a cheaper path for it?
            neighbors = new Bag();
            locations.getNeighborsHamiltonianDistance(
                t.l.x, t.l.y, 1, false, neighbors, null, null);

            // ...find the CHEAPEST neighbor
            int bestCost = myCost;
            Location bestNeighbor = null;

            for (Object p : neighbors)
            {
                Location n = (Location) p;
                if (n == t.l)
                {
                    continue; // don't recheck!
                }
                int hisCost = result.get(n.x, n.y);

                // if the neighbor offers a better path, take it!
                if (hisCost + myType < bestCost)
                {
                    bestCost = hisCost + myType;
                    bestNeighbor = n;
                }
            }

            // if there was such a cheapest neighbor, reset our cost!
            if (bestNeighbor != null)
            { // we have found a better path
                result.set(t.l.x, t.l.y, bestCost);
                myCost = bestCost;
            }

            // check to see if any neighbors should route this THIS tile
            for (Object p : neighbors)
            {
                Location n = (Location) p;
                if (n == t.l)
                {
                    continue; // don't recheck!
                }
                int hisCost = result.get(n.x, n.y);
                int hisType = roadCost(n, roads);

                // if this tile offers a better path for the neighbor, tell the neighbor
                // and submit it so it rechecks its neighbors and options
                if (myCost + hisType < hisCost)
                {
                    int hisNewCost = bestCost + hisType;
                    result.set(n.x, n.y, hisNewCost);
                    if (!doNotAdd.contains(n))
                    {
                        queue.add(new TileWrapper(n, hisNewCost));
                        doNotAdd.add(n);
                    }

                }
            }

        }


        return result;
    }


    int roadCost(Location l, IntGrid2D roads)
    {
        if (roads.get(l.x, l.y) > 0)
        {
            return (int) Agent.ENERGY_TO_WALK_PAVED;
        }
        return (int) Agent.ENERGY_TO_WALK_UNPAVED;
    }



    class TileWrapper implements Comparable
    {

        Location l;


        int value;


        public TileWrapper(Location l, int value)
        {
            this.l = l;
            this.value = value;
        }


        @Override
        public int compareTo(Object arg0)
        {
            TileWrapper t = (TileWrapper) arg0;
            if (value < t.value)
            {
                return 1;
            } else if (value > t.value)
            {
                return -1;
            }
            return 0;
        }


    }
}
